Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement optional ring buffer for container logs #28762

Merged
merged 2 commits into from Feb 1, 2017

Conversation

cpuguy83
Copy link
Member

This allows the user to set a logging mode to "blocking" (default), or
"non-blocking", which uses the ring buffer as a proxy to the real log
driver.

This allows a container to never be blocked on stdio at the cost of
dropping log messages.

Introduces a new flag to docker run and dockerd, --log-mode, which
takes a value of "blocking", or "non-blocking". I chose not to implement
this as a bool since it is difficult to determine if the mode was set to
false vs just not set... especially difficult when merging the default
daemon config with the container config.

One thing to think about here is logger.Message could be any size, it just depends on how long the log line was... so it could be almost nothing, or it could be several megabytes which could cause problems in just blindly buffering N number of messages in the ring buffer.
We could keep track of this, but this could be tricky and introduces locking into the implementation.

@justincormack
Copy link
Contributor

Seems like one reasonable solution. I wonder if the ring buffer should be a fixed size in bytes though (which can be specified). Then this would carry over simply to a compatible interfaces via a pipe, where you could set the pipe buffer size and discard if no space. There are some issues about message boundaries though, you could use datagrams and allow truncation.

@LK4D4
Copy link
Contributor

LK4D4 commented Nov 23, 2016

@cpuguy83 maybe embed it to log-opt?

@cpuguy83
Copy link
Member Author

@cpuguy83 maybe embed it to log-opt?

@LK4D4 The reason I didn't want to do that is log-opt tends to be options for the driver, where as this is changing how docker itself handles logs.

default:
select {
case <-r.ctx.Done():
case <-r.queue: // Make room on the queue
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May need to add a lock to protect against another log message coming in right after this but before we get a chance to queue below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd better try to implement messages ring buffer as a separate data structure with tests and stuff. Probably that was your plan after design review.

@LK4D4
Copy link
Contributor

LK4D4 commented Nov 23, 2016

I agree with @justincormack about specifying size. However, computing size might be challenging. The number of messages might be okay, too for average use.
Design +1

@AkihiroSuda
Copy link
Member

Design SGTM 👍

@cpuguy83 cpuguy83 force-pushed the logger_ring_buffer branch 2 times, most recently from 20366c4 to 52626ee Compare November 25, 2016 23:32
@thaJeztah
Copy link
Member

One thing to think about here is logger.Message could be any size, it just depends on how long the log line was...

Perhaps silly comment, but https://github.com/docker/docker/pull/22982/files#diff-0e0dba78caf4a57795625b5ce9943c9eR13 introduced a buffer size, i.e., messages longer than that size are split (even if there's no newline); or is that not relevant here?

@cpuguy83
Copy link
Member Author

That's true, there is a max. But I think people want to control the buffer based on size not number of messages.
So I've pushed a new implementation doing just this.

@cpuguy83
Copy link
Member Author

@justincormack Implementing this in github.com/pkg/iotutils.BytesPipe is fairly trivial I think, but wondering what's better, drop random byte slices or full messages.

@cpuguy83
Copy link
Member Author

Although certainly implementing in BytesPipe is significantly faster since we don't need to do any extra allocations, where as each call to Log() as implemented here requires 1 allocation.

I went ahead and added an implementation in BytesPipe just to look at, presented without tests or benchmarks at the moment.

@justincormack
Copy link
Contributor

justincormack commented Nov 26, 2016 via email

// the passed in logger.
func NewRingLogger(driver Logger, logCtx Context, maxSize int64) Logger {
if maxSize < 0 {
maxSize = defaultRingMaxSize
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: personally, I prefer zero-value for indicating the default

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I wanted to support a 0 size, which would essentially mean store no more than 1 message.

flags.Var(&copts.loggingOpts, "log-opt", "Log driver options")
flags.StringVar(&copts.volumeDriver, "volume-driver", "", "Optional volume driver for the container")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unintended change, perhaps?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intended, just re-ordered so log-opt is underneath log-driver.

@cpuguy83
Copy link
Member Author

Removed the commit with the bytespipe changes.

@@ -176,7 +176,9 @@ func (l *logStream) Log(msg *logger.Message) error {
defer l.lock.RUnlock()
if !l.closed {
// buffer up the data, making sure to copy the Line data
l.messages <- logger.CopyMessage(msg)
var copy logger.Message
copy = *msg
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't clone underlying message([]byte), which was whole point IIRC.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to clone the message bytes because that won't ever change.
It's the passed in *Message which gets changed by the log copier since it keeps a single message pointer for all messages.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copier uses parts of its own buffer for Line:
https://github.com/docker/docker/blob/master/daemon/logger/copier.go#L81
And drivers like fluentd can collect messages for later send, so their Line will be overriden by copier.
Also, I believe this was in comment to CopyMessage :D

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh you're right. I'll bring this back.

package logger

import (
"context"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be net/context

@LK4D4
Copy link
Contributor

LK4D4 commented Nov 28, 2016

Cond based buffer is kinda cool. Will review later today.

@cpuguy83 cpuguy83 deleted the logger_ring_buffer branch February 2, 2017 13:30
cpuguy83 added a commit to cpuguy83/docker.github.io that referenced this pull request Feb 2, 2017
This functionality was added in moby/moby#28762.
It allows a user to specify what to do in cases when a log message can't
be sent, block the container's stdio or drop messages.

Also removes an erronious entry about a `nats` log driver, which does
not exist.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
mdlinville pushed a commit to docker/docs that referenced this pull request Mar 8, 2017
This functionality was added in moby/moby#28762.
It allows a user to specify what to do in cases when a log message can't
be sent, block the container's stdio or drop messages.

Also removes an erronious entry about a `nats` log driver, which does
not exist.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
mdlinville pushed a commit to docker/docs that referenced this pull request Mar 17, 2017
This functionality was added in moby/moby#28762.
It allows a user to specify what to do in cases when a log message can't
be sent, block the container's stdio or drop messages.

Also removes an erronious entry about a `nats` log driver, which does
not exist.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
JimGalasyn pushed a commit to docker/docs that referenced this pull request Mar 23, 2017
This functionality was added in moby/moby#28762.
It allows a user to specify what to do in cases when a log message can't
be sent, block the container's stdio or drop messages.

Also removes an erronious entry about a `nats` log driver, which does
not exist.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
mdlinville pushed a commit to docker/docs that referenced this pull request Mar 30, 2017
This functionality was added in moby/moby#28762.
It allows a user to specify what to do in cases when a log message can't
be sent, block the container's stdio or drop messages.

Also removes an erronious entry about a `nats` log driver, which does
not exist.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
mdlinville pushed a commit to docker/docs that referenced this pull request Mar 31, 2017
This functionality was added in moby/moby#28762.
It allows a user to specify what to do in cases when a log message can't
be sent, block the container's stdio or drop messages.

Also removes an erronious entry about a `nats` log driver, which does
not exist.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
mdlinville pushed a commit to docker/docs that referenced this pull request Apr 5, 2017
This functionality was added in moby/moby#28762.
It allows a user to specify what to do in cases when a log message can't
be sent, block the container's stdio or drop messages.

Also removes an erronious entry about a `nats` log driver, which does
not exist.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
mdlinville pushed a commit to docker/docs that referenced this pull request Apr 5, 2017
This functionality was added in moby/moby#28762.
It allows a user to specify what to do in cases when a log message can't
be sent, block the container's stdio or drop messages.

Also removes an erronious entry about a `nats` log driver, which does
not exist.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
mdlinville pushed a commit to docker/docs that referenced this pull request Apr 6, 2017
This functionality was added in moby/moby#28762.
It allows a user to specify what to do in cases when a log message can't
be sent, block the container's stdio or drop messages.

Also removes an erronious entry about a `nats` log driver, which does
not exist.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
@glasser
Copy link
Contributor

glasser commented Jun 26, 2017

Based on reading the source, it looks like there's a serious bug here in the journald and syslog drivers. (Haven't managed to verify yet with an actual built version.) In both of those drivers, you call logger.PutMessage(msg) and then afterwards read msg.Source. But PutMessage calls msg.reset() which clears Source. Am I missing something or do the drivers not need to save those values before calling PutMessage?

@cpuguy83
Copy link
Member Author

@glasser PutMessage should only be called after the value has already been copied.

For instance:

line := string(msg.Source)

This copies the value of msg.Source rather than the reference to msg.Source.

@glasser
Copy link
Contributor

glasser commented Jun 26, 2017

I agree. But look at this code this PR put into the syslog driver, which remains there to this day:

func (s *syslogger) Log(msg *logger.Message) error {
	line := string(msg.Line)
	logger.PutMessage(msg)
	if msg.Source == "stderr" {
		return s.writer.Err(line)
	}
	return s.writer.Info(line)
}

That sure seems to read from Source after calling PutMessage. Similar code exists in the journald driver.

@cpuguy83
Copy link
Member Author

@glasser Yeah, that looks like an issue!
Do you want to open a PR to fix this?

glasser added a commit to glasser/moby that referenced this pull request Jun 26, 2017
logger.PutMessage, added in moby#28762 (v17.04.0-ce), clears msg.Source. So journald
and syslog were treating stderr messages as if they were stdout.

Signed-off-by: David Glasser <glasser@davidglasser.net>
@glasser
Copy link
Contributor

glasser commented Jun 26, 2017

@cpuguy83 sure, #33832

skuenzli added a commit to skuenzli/docker.github.io that referenced this pull request Dec 24, 2017
…g options.

Documentation based on:
* implementation and comments of the [Ring Logger](https://github.com/moby/moby/blob/master/daemon/logger/ring.go#L49)
* conversation in implementing [PR 28762](moby/moby#28762)
skuenzli added a commit to skuenzli/docker.github.io that referenced this pull request Dec 25, 2017
…g options.

Documentation based on:
* implementation and comments of the [Ring Logger](https://github.com/moby/moby/blob/master/daemon/logger/ring.go#L49)
* conversation in implementing [PR 28762](moby/moby#28762)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants